package com.ilxqx.fierce.core;

import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 动作参数
 *
 * @author Venus
 * @since 1.0.0
 */
public class ActionMethodParameter extends SynthesizingMethodParameter {

    @Nullable
    private volatile Annotation[] combinedAnnotations;
    @Nullable
    private volatile List<Annotation[][]> interfaceParameterAnnotations;
    private final Class<?> target;
    private final Method method;

    public ActionMethodParameter(Class<?> target, Method method, int index) {
        super(method, index);
        this.target = target;
        this.method = method;
    }

    protected ActionMethodParameter(ActionMethodParameter original) {
        super(original);
        this.interfaceParameterAnnotations = original.interfaceParameterAnnotations;
        this.combinedAnnotations = original.combinedAnnotations;
        this.target = original.target;
        this.method = original.method;
    }

    @Override
    @NonNull
    public Method getMethod() {
        return this.method;
    }

    @Override
    @NonNull
    public Class<?> getContainingClass() {
        return this.target;
    }

    @Override
    public <T extends Annotation> T getMethodAnnotation(@NonNull Class<T> annotationType) {
        return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
    }

    @Override
    public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
        return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
    }

    @Override
    @NonNull
    public Annotation[] getParameterAnnotations() {
        Annotation[] anns = this.combinedAnnotations;
        if (anns == null) {
            anns = super.getParameterAnnotations();
            int index = getParameterIndex();
            if (index >= 0) {
                for (Annotation[][] ifcAnns : this.getInterfaceParameterAnnotations()) {
                    if (index < ifcAnns.length) {
                        Annotation[] paramAnns = ifcAnns[index];
                        if (paramAnns.length > 0) {
                            List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
                            merged.addAll(Arrays.asList(anns));
                            for (Annotation paramAnn : paramAnns) {
                                boolean existingType = false;
                                for (Annotation ann : anns) {
                                    if (ann.annotationType() == paramAnn.annotationType()) {
                                        existingType = true;
                                        break;
                                    }
                                }
                                if (!existingType) {
                                    merged.add(adaptAnnotation(paramAnn));
                                }
                            }
                            anns = merged.toArray(new Annotation[0]);
                        }
                    }
                }
            }
            this.combinedAnnotations = anns;
        }
        return anns;
    }

    private List<Annotation[][]> getInterfaceParameterAnnotations() {
        List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
        if (parameterAnnotations == null) {
            parameterAnnotations = new ArrayList<>();
            for (Class<?> ifc : ClassUtils.getAllInterfacesForClassAsSet(this.method.getDeclaringClass())) {
                for (Method candidate : ifc.getMethods()) {
                    if (isOverrideFor(candidate)) {
                        parameterAnnotations.add(candidate.getParameterAnnotations());
                    }
                }
            }
            this.interfaceParameterAnnotations = parameterAnnotations;
        }
        return parameterAnnotations;
    }

    private boolean isOverrideFor(Method candidate) {
        if (!candidate.getName().equals(this.method.getName()) ||
            candidate.getParameterCount() != this.method.getParameterCount()) {
            return false;
        }
        Class<?>[] paramTypes = this.method.getParameterTypes();
        if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) {
            return true;
        }
        for (int i = 0; i < paramTypes.length; i++) {
            if (paramTypes[i] !=
                ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
                return false;
            }
        }
        return true;
    }

    @Override
    @NonNull
    public ActionMethodParameter clone() {
        return new ActionMethodParameter(this);
    }
}
